/*
    GNU GENERAL PUBLIC LICENSE
    Copyright (C) 2006 The Lobo Project

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public
    License as published by the Free Software Foundation; either
    verion 2 of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    You should have received a copy of the GNU General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Contact info: lobochief@users.sourceforge.net
*/
package org.lobobrowser.main;

import java.io.*;
import java.util.*;
import java.awt.*;
import java.net.*;

import org.lobobrowser.clientlet.*;
import org.lobobrowser.ua.*;
import org.lobobrowser.util.*;

import java.util.logging.*;

/**
 * Manages platform extensions.
 */
public class ExtensionManager {
	private static final ExtensionManager instance = new ExtensionManager();
	private static final String EXT_DIR_NAME = "ext";
	
	//Note: We do not synchronize around the extensions collection, 
	//given that it is fully built in the constructor.
	private final Map<String,Extension> extensionById = new HashMap<String,Extension>();
	private final SortedSet<Extension> extensions = new TreeSet<Extension>();
	private final ArrayList<Extension> libraries = new ArrayList<Extension>();
	
	private ExtensionManager() {
//		this.createExtensions();
		this.createHardCodeExtensions();
	}
	
	public static ExtensionManager getInstance() {
		// This security check should be enough, provided
		// ExtensionManager instances are not retained. 
 		SecurityManager sm = System.getSecurityManager();
 		if(sm != null) {
 			sm.checkPermission(org.lobobrowser.security.GenericLocalPermission.EXT_GENERIC);
 		}
		return instance;
	}

	private void createExtensions() {
		File[] extDirs;
		File[] extFiles;
		String extDirsProperty = System.getProperty("java.ext.dirs");

		File appDir2 = PlatformInit.getInstance().getApplicationDirectory();

		if(extDirsProperty != null) {
			File appDir = PlatformInit.getInstance().getApplicationDirectory();
			//TODO JF: la linea que esta abajo de todo este comentario es la que hay 
				//que cambiar por:
					// extDirs = new File[] { appDir };
			extDirs = new File[] { new File(".") };
		}
		else {

			StringTokenizer tok = new StringTokenizer(extDirsProperty, ",");
			ArrayList<File> extDirsList = new ArrayList<File>();
			while(tok.hasMoreTokens()) {
				String token = tok.nextToken();

				extDirsList.add(new File(token.trim()));
			}
			extDirs = extDirsList.toArray(new File[0]);
		}
		String extFilesProperty = System.getProperty("java.ext.files");
		if(extFilesProperty == null) {
			extFiles = new File[0];
		}
		else {
			StringTokenizer tok = new StringTokenizer(extFilesProperty, ",");
			ArrayList<File> extFilesList = new ArrayList<File>();
			while(tok.hasMoreTokens()) {
				String token = tok.nextToken();
				extFilesList.add(new File(token.trim()));
			}
			extFiles = extFilesList.toArray(new File[0]);			
		}
		this.createExtensions(extDirs, extFiles);
	}

	
	private void createHardCodeExtensions(){
		
		Collection<Extension> extensions = this.extensions;
		Collection<Extension> libraries = this.libraries;
		Map<String,Extension> extensionById = this.extensionById;
		extensions.clear();
		libraries.clear();
		extensionById.clear();
		
		addExtension("org.lobobrowser.primary.ext.ExtensionImpl", "Lobo's Primary Extension", 10, true );
		addExtension("org.lobobrowser.jweb.ext.NavigatorExtensionImpl", "Java Web Content", 7, false );
		
	ClassLoader rootClassLoader = this.getClass().getClassLoader();
		
		// Create class loader for extension "libraries"		
		ArrayList<URL> libraryURLCollection = new ArrayList<URL>();
		for(Extension ei : libraries) {
			try {
				libraryURLCollection.add(ei.getCodeSource());

			} catch(java.net.MalformedURLException thrown) {
			}
		}

		ClassLoader librariesCL = new URLClassLoader(libraryURLCollection.toArray(new URL[0]), rootClassLoader);		
				
		// Initialize class loader in each extension, using librariesCL as
		// the parent class loader. Extensions are initialized in parallel.
		Collection<JoinableTask> tasks = new ArrayList<JoinableTask>();
		PlatformInit pm = PlatformInit.getInstance();
		for(Extension ei : extensions) {
			final ClassLoader pcl = librariesCL;
			final Extension fei = ei;
			// Initialize rest of them in parallel.
			JoinableTask task = new JoinableTask() {
				public void execute() {
					try {

						fei.initClassLoader(pcl);
					} catch(Exception err) {
					}
				}

				public String toString() {
					return "createExtensions:" + fei;
				}
			};
			tasks.add(task);
			pm.scheduleTask(task);
		}

		// Join tasks to make sure all extensions are
		// initialized at this point.
		for(JoinableTask task : tasks) {
			try {
				task.join();
			} catch(InterruptedException ie) {
				// ignore
			}
		}
	}
	
	private void addExtension(File file) throws java.io.IOException {
		if(!file.exists()) {
			return;
		}
		
		Extension ei = new Extension(file);
		this.extensionById.put(ei.getId(), ei);
		if(ei.isLibraryOnly()) {

			libraries.add(ei);
		}
		else {

			extensions.add(ei);
		}		
	}
	
	private void addExtension(String nombreClase, String extID, int prioridad, boolean primario){
		
		Extension ei = new Extension(nombreClase,extID,prioridad,primario);
		this.extensionById.put(ei.getId(), ei);
		if(ei.isLibraryOnly()) {

			libraries.add(ei);
		}
		else {

			extensions.add(ei);
		}
	}
	
	
	private void createExtensions(File[] extDirs, File[] extFiles) {
		Collection<Extension> extensions = this.extensions;
		Collection<Extension> libraries = this.libraries;
		Map<String,Extension> extensionById = this.extensionById;
		extensions.clear();
		libraries.clear();
		extensionById.clear();
		for(File extDir : extDirs) {
			if(!extDir.exists()) {
				if(PlatformInit.getInstance().isCodeLocationDirectory()) {
				}
				continue;
			}
			File[] extRoots = extDir.listFiles(new ExtFileFilter());
			if(extRoots == null || extRoots.length == 0) {
				continue;
			}
			for(File file : extRoots) {
				try {
					this.addExtension(file);
				} catch(IOException ioe) {
				}
			}

		}
		for(File file : extFiles) {
			try {
				this.addExtension(file);
			} catch(IOException ioe) {
			}				
		}

		if(this.extensionById.size() == 0) {		}
		
		// Get the system class loader
		ClassLoader rootClassLoader = this.getClass().getClassLoader();
		
		// Create class loader for extension "libraries"		
		ArrayList<URL> libraryURLCollection = new ArrayList<URL>();
		for(Extension ei : libraries) {
			try {
				libraryURLCollection.add(ei.getCodeSource());
			} catch(java.net.MalformedURLException thrown) {
			}
		}

		ClassLoader librariesCL = new URLClassLoader(libraryURLCollection.toArray(new URL[0]), rootClassLoader);		
				
		// Initialize class loader in each extension, using librariesCL as
		// the parent class loader. Extensions are initialized in parallel.
		Collection<JoinableTask> tasks = new ArrayList<JoinableTask>();
		PlatformInit pm = PlatformInit.getInstance();
		for(Extension ei : extensions) {
			final ClassLoader pcl = librariesCL;
			final Extension fei = ei;
			// Initialize rest of them in parallel.
			JoinableTask task = new JoinableTask() {
				public void execute() {
					try {
						fei.initClassLoader(pcl);
					} catch(Exception err) {
					}
				}

				public String toString() {
					return "createExtensions:" + fei;
				}
			};
			tasks.add(task);
			pm.scheduleTask(task);
		}

		// Join tasks to make sure all extensions are
		// initialized at this point.
		for(JoinableTask task : tasks) {
			try {
				task.join();
			} catch(InterruptedException ie) {
				// ignore
			}
		}
	}

	public ClassLoader getClassLoader(String extensionId) {
		Extension ei = this.extensionById.get(extensionId);
		if(ei != null) {
			return ei.getClassLoader();
		}
		else {
			return null;
		}
	}
	
	public void initExtensions() {
		Collection<JoinableTask> tasks = new ArrayList<JoinableTask>();
		PlatformInit pm = PlatformInit.getInstance();
		for(Extension ei : this.extensions) {
			final Extension fei = ei;
			JoinableTask task = new JoinableTask() {
				public void execute() {
					fei.initExtension();
				}

				public String toString() {
					return "initExtensions:" + fei;
				}
			};
			tasks.add(task);
			pm.scheduleTask(task);
		}
		// Join all tasks before returning
		for(JoinableTask task : tasks) {
			try {
				task.join();
			} catch(InterruptedException ie) {
				// ignore
			}
		}
	}
		
	public void initExtensionsWindow(final NavigatorWindow context) {
		// This must be done sequentially due to menu lookup infrastructure.
		for(Extension ei : this.extensions) {
			try {
				ei.initExtensionWindow(context);
			} catch(Exception err) {
			}
		}
	}

	public void shutdownExtensionsWindow(final NavigatorWindow context) {
		// This must be done sequentially due to menu lookup infrastructure.
		for(Extension ei : this.extensions) {
			try {				
				ei.shutdownExtensionWindow(context);
			} catch(Exception err) {			}
		}
	}

	public Clientlet getClientlet(ClientletRequest request, ClientletResponse response) {
		Collection<Extension> extensions = this.extensions;
		// Call all plugins once to see if they can select the response.
		for(Extension ei : extensions) {
			try {
				Clientlet clientlet = ei.getClientlet(request, response);
				if(clientlet != null) {
					return clientlet;
				}
			} catch(Exception thrown) {
			}
		}

		// None handled it. Call the last resort handlers in reverse order.
		for(Extension ei : (Collection<Extension>) org.lobobrowser.util.CollectionUtilities.reverse(extensions)) {
			try {
				Clientlet clientlet = ei.getLastResortClientlet(request, response);
				if(clientlet != null) {
					return clientlet;
				}
			} catch(Exception thrown) {
			}
		}		
		return null;
	}
	
	public void handleError(NavigatorFrame frame, final ClientletResponse response, final Throwable exception) {
		final NavigatorExceptionEvent event = new NavigatorExceptionEvent(this, NavigatorEventType.ERROR_OCCURRED, frame, response, exception);
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				Collection<Extension> ext = extensions;
				// Call all plugins once to see if they can select the response.
				boolean dispatched = false;
				for(Extension ei : ext) {
					if(ei.handleError(event)) {
						dispatched = true;
					}
				}

			}
		});
	}
	
	public void dispatchBeforeNavigate(NavigationEvent event) throws NavigationVetoException {
		for(Extension ei : extensions) {
			try {
				ei.dispatchBeforeLocalNavigate(event);
			} catch(NavigationVetoException nve) {
				throw nve;
			} catch(Exception other) {
			}
		}
	}

	public void dispatchBeforeLocalNavigate(NavigationEvent event) throws NavigationVetoException {
		for(Extension ei : extensions) {
			try {
				ei.dispatchBeforeLocalNavigate(event);
			} catch(NavigationVetoException nve) {
				throw nve;
			} catch(Exception other) {
			}
		}
	}

	public void dispatchBeforeWindowOpen(NavigationEvent event) throws NavigationVetoException {
		for(Extension ei : extensions) {
			try {
				ei.dispatchBeforeWindowOpen(event);
			} catch(NavigationVetoException nve) {
				throw nve;
			} catch(Exception other) {
			}
		}
	}

	public URLConnection dispatchPreConnection(URLConnection connection) {
		for(Extension ei : extensions) {
			try {
				connection = ei.dispatchPreConnection(connection);
			} catch(Exception other) {
			}
		}
		return connection;
	}

	public URLConnection dispatchPostConnection(URLConnection connection) {
		for(Extension ei : extensions) {
			try {
				connection = ei.dispatchPostConnection(connection);
			} catch(Exception other) {
			}
		}
		return connection;
	}

	
	private static class ExtFileFilter implements FileFilter {
		public boolean accept(File file) {
			//return file.isDirectory() || file.getName().toLowerCase().endsWith(".jar");
			return file.getName().toLowerCase().endsWith(".jar");
		}
	}
}
